<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed!');

/**
 *	Thumb()
 *
 *	Library for image resizing and image cropping
 *	
 *	@author 	JOKOWOW
 */

if ( ! defined('DS')) define('DS', DIRECTORY_SEPARATOR);
if ( ! defined('MEMORY_TO_ALLOCATE')) define('MEMORY_TO_ALLOCATE', '500M');
if ( ! defined('DEFAULT_QUALITY')) define('DEFAULT_QUALITY', 90);
if ( ! defined('CURRENT_DIR')) define('CURRENT_DIR', dirname(__FILE__));
if ( ! defined('CACHE_DIR_NAME')) define('CACHE_DIR_NAME', APPPATH . '/cache/');
if ( ! defined('CACHE_DIR')) define('CACHE_DIR', CACHE_DIR_NAME);
if ( ! defined('DOCUMENT_ROOT')) define('DOCUMENT_ROOT', $_SERVER['DOCUMENT_ROOT']);

class Thumb {

    private $CI;
    private $image   = '';
    private $width   = 100;
    private $height  = 100;
    private $ratio   = '1:1';
    private $color   = '';
    private $quality = 90;
    private $nocache = TRUE;

	function __construct($config = array())
	{
		$this->CI =& get_instance();
		$this->initialize($config);
	}

	function initialize($config)
	{
		if (count($config) > 0)
			foreach($config as $k => $v)
				$this->$k = $v;
	}

	function render()
	{
		$image = $this->image;

		if (empty($image))
        {
            header('HTTP/1.1 400 Bad Request');
            echo 'Error: no image was specified';
            exit();
        }
        
        if ( ! $image)
        {
            header('HTTP/1.1 400 Bad Request');
            echo 'Error: no image was specified';
            exit();
        }
        
        if ( ! file_exists($image))
        {
                header('HTTP/1.1 404 Not Found');
                echo 'Error: image does not exist: ' . $image;
                exit();
        }
        
		$size = GetImageSize($image);
		$mime = $size['mime'];
        
        if (substr($mime, 0, 6) != 'image/')
        {
            header('HTTP/1.1 400 Bad Request');
            echo 'Error: requested file is not an accepted type: ' . $image;
            exit();
        }
        
		$width     = $size[0];
		$height    = $size[1];
		$maxWidth  = $this->width;
		$maxHeight = $this->height;

        $color = empty($color) ? FALSE : preg_replace('/[^0-9a-fA-F]/', '', (string) $this->color);

        if (!$maxWidth && $maxHeight)
            $maxWidth   = 99999999999999;
        elseif ($maxWidth && !$maxHeight)
            $maxHeight  = 99999999999999;
        elseif ($color && !$maxWidth && !$maxHeight)
        {
            $maxWidth	= $width;
            $maxHeight	= $height;
        }
        
        if ((!$maxWidth && !$maxHeight) || (!$color && $maxWidth >= $width && $maxHeight >= $height))
        {
            $data	= file_get_contents($image);
            $lastModifiedString	= gmdate('D, d M Y H:i:s', filemtime($image)) . ' GMT';
            $etag	= md5($data);
            $this->get_header_info($etag, $lastModifiedString);
            header("Content-type: $mime");
            header('Content-Length: ' . strlen($data));
            echo $data;
            exit();
        }
        
        // Ratio cropping
        $offsetX	= 0;
        $offsetY	= 0;
        
        if ( ! empty($this->ratio))
        {
            $cropRatio = explode(':', (string) $this->ratio);
            
            if (count($cropRatio) == 2)
            {
                $ratioComputed      = $width / $height;
                $cropRatioComputed  = (float) $cropRatio[0] / (float) $cropRatio[1];

                if ($ratioComputed < $cropRatioComputed)
                { 
                    // Image is too tall so we will crop the top and bottom
                    $origHeight = $height;
                    $height     = $width / $cropRatioComputed;
                    $offsetY    = ($origHeight - $height) / 2;
                }
                else if ($ratioComputed > $cropRatioComputed)
                { 
                    // Image is too wide so we will crop off the left and right sides
                    $origWidth  = $width;
                    $width      = $height * $cropRatioComputed;
                    $offsetX    = ($origWidth - $width) / 2;
                }
            }    
        }

        $xRatio		= $maxWidth / $width;
        $yRatio		= $maxHeight / $height;
        
        if ($xRatio * $height < $maxHeight)
        { 
            // Resize the image based on width
            $tnHeight	= ceil($xRatio * $height);
            $tnWidth	= $maxWidth;
        }
        else 
        {
            // Resize the image based on height
            $tnWidth	= ceil($yRatio * $width);
            $tnHeight	= $maxHeight;
        }
        
        $quality	= empty($this->quality) ?  DEFAULT_QUALITY : (int) $this->quality;
        
        $resizedImageSource		= $tnWidth . 'x' . $tnHeight . 'x' . $quality;

        if ($color)
            $resizedImageSource	.= 'x' . $color;

        if ( ! empty($this->ratio))
            $resizedImageSource	.= 'x' . (string) $this->ratio;

        $resizedImageSource .= '-' . $image;
        $resizedImage       = md5($resizedImageSource);
        $resized            = CACHE_DIR . $resizedImage;
        
        if ($this->nocache == TRUE && file_exists($resized))
        {
            $imageModified  = filemtime($image);
            $thumbModified  = filemtime($resized);

            if($imageModified < $thumbModified) {

                $data    = file_get_contents($resized);
                $lastModifiedString = gmdate('D, d M Y H:i:s', $thumbModified) . ' GMT';
                $etag               = md5($data);
                $this->get_header_info($etag, $lastModifiedString);

                // header("Content-type: $mime");
                // header('Content-Length: ' . strlen($data));
                // echo $data;
                // exit();
                
                $this->serve_file_resumable($resized, $mime);

            }
        }

        ini_set('memory_limit', MEMORY_TO_ALLOCATE);
        $dst	= imagecreatetruecolor($tnWidth, $tnHeight);
        
        switch ($size['mime'])
        {
            case 'image/gif':
                // We will be converting GIFs to PNGs to avoid transparency issues when resizing GIFs
                // This is maybe not the ideal solution, but IE6 can suck it
                $creationFunction	= 'ImageCreateFromGif';
                $outputFunction		= 'ImagePng';
                $mime				= 'image/png'; // We need to convert GIFs to PNGs
                $doSharpen			= FALSE;
                $quality			= round(10 - ($quality / 10)); // We are converting the GIF to a PNG and PNG needs a compression level of 0 (no compression) through 9
            break;

            case 'image/x-png':
            case 'image/png':
                $creationFunction	= 'ImageCreateFromPng';
                $outputFunction		= 'ImagePng';
                $doSharpen			= FALSE;
                $quality			= round(10 - ($quality / 10)); // PNG needs a compression level of 0 (no compression) through 9
            break;

            default:
                $creationFunction	= 'ImageCreateFromJpeg';
                $outputFunction	 	= 'ImageJpeg';
                $doSharpen			= TRUE;
            break;
        }
        
        $src	= $creationFunction($image);
        
        if (in_array($size['mime'], array('image/gif', 'image/png')))
        {
            if (!$color)
            {
                // If this is a GIF or a PNG, we need to set up transparency
                imagealphablending($dst, false);
                imagesavealpha($dst, true);
            }
            else
            {
                // Fill the background with the specified color for matting purposes
                if ($color[0] == '#')
                        $color = substr($color, 1);

                $background	= FALSE;

                if (strlen($color) == 6)
                        $background	= imagecolorallocate($dst, hexdec($color[0].$color[1]), hexdec($color[2].$color[3]), hexdec($color[4].$color[5]));
                else if (strlen($color) == 3)
                        $background	= imagecolorallocate($dst, hexdec($color[0].$color[0]), hexdec($color[1].$color[1]), hexdec($color[2].$color[2]));
                if ($background)
                        imagefill($dst, 0, 0, $background);
            }
        }
        
        ImageCopyResampled($dst, $src, 0, 0, $offsetX, $offsetY, $tnWidth, $tnHeight, $width, $height);
        
        if ($doSharpen)
        {
            // Sharpen the image based on two things:
            //	(1) the difference between the original size and the final size
            //	(2) the final size
            $sharpness	= $this->sharp($width, $tnWidth);

            $sharpenMatrix	= array(
                    array(-1, -2, -1),
                    array(-2, $sharpness + 12, -2),
                    array(-1, -2, -1)
            );
            $divisor		= $sharpness;
            $offset			= 0;
            imageconvolution($dst, $sharpenMatrix, $divisor, $offset);
        }
        
        if (!file_exists(CACHE_DIR)) mkdir(CACHE_DIR, 0755);
        
        if (!is_readable(CACHE_DIR))
        {
            header('HTTP/1.1 500 Internal Server Error');
            echo 'Error: the cache directory is not readable';
            exit();
        }
        else if (!is_writable(CACHE_DIR))
        {
            header('HTTP/1.1 500 Internal Server Error');
            echo 'Error: the cache directory is not writable';
            exit();
        }
        
        $outputFunction($dst, $resized, $quality);
        
        ob_start();
        $outputFunction($dst, null, $quality);
        $data	= ob_get_contents();
        ob_end_clean();
        
        ImageDestroy($src);
        ImageDestroy($dst);
        
        $lastModifiedString	= gmdate('D, d M Y H:i:s', filemtime($resized)) . ' GMT';
        $etag				= md5($data);
        
        $this->get_header_info($etag, $lastModifiedString);
        
        header("Content-type: $mime");
        header('Content-Length: ' . strlen($data));
        echo $data;
	}

	function get_header_info($etag, $lastModified)
	{

        $etag_str = $etag;

        if (isset($_SERVER['HTTP_ACCEPT_ENCODING']) && 
            strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE) 
            $etag_str = $etag.'-gzip';

        header("Last-Modified: $lastModified");
        header("ETag: \"{$etag}\"");

        $if_none_match = isset($_SERVER['HTTP_IF_NONE_MATCH']) ?
            stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) : 
            false;

        $if_modified_since = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ?
            stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']) :
            false;

        if ( ! $if_modified_since &&  ! $if_none_match)
            return;

        if ($if_none_match && $if_none_match != $etag_str && $if_none_match != '"' . $etag_str . '"')
            return;

        if ($if_modified_since && $if_modified_since != $lastModified)
            return;

        header('HTTP/1.1 304 Not Modified');
        exit();

		header("Last-Modified: $lastModified");
        header("ETag: \"{$etag}\"");
        
	}

	function sharp($orig, $final) // function from Ryan Rud (http://adryrun.com)
    {
        $final	= $final * (750.0 / $orig);
        $a		= 52;
        $b		= -0.27810650887573124;
        $c		= .00047337278106508946;

        $result = $a + $b * $final + $c * $final * $final;

        return max(round($result), 0);
    }

    function serve_file_resumable ($file, $contenttype = 'application/octet-stream', $output = 'display') {

        @error_reporting(0);

        if ( ! file_exists($file)) {
            header("HTTP/1.1 404 Not Found");
            exit;
        }

        if (isset($_SERVER['HTTP_RANGE'])) 
            $range = $_SERVER['HTTP_RANGE'];
        else if ($apache = apache_request_headers()) 
        { 
            $headers = array();
            
            foreach ($apache as $header => $val) 
                $headers[strtolower($header)] = $val;

            if (isset($headers['range'])) 
                $range = $headers['range'];
            else 
                $range = FALSE;
        } 
        else 
            $range = FALSE;

        

        $filesize = filesize($file);
        
        if ($range) {
            $partial = true;

            list($param,$range) = explode('=',$range);

            if (strtolower(trim($param)) != 'bytes') 
            { 
                header("HTTP/1.1 400 Invalid Request");
                exit;
            }

            $range = explode(',',$range);
            $range = explode('-',$range[0]);

            if (count($range) != 2) 
            {
                header("HTTP/1.1 400 Invalid Request");
                exit;
            }

            if ($range[0] === '') 
            {
                $end = $filesize - 1;
                $start = $end - intval($range[0]);
            } 
            else if ($range[1] === '') 
            {
                $start = intval($range[0]);
                $end = $filesize - 1;
            } 
            else 
            {
                $start = intval($range[0]);
                $end = intval($range[1]);
                if ($end >= $filesize || (!$start && (!$end || $end == ($filesize - 1)))) 
                    $partial = false;
            }      
            
            $length = $end - $start + 1;

        } else 
            $partial = false;

        header("Content-Type: $contenttype");
        header("Content-Length: $filesize");

        if ($output == 'download')
            header('Content-Disposition: attachment; filename="'.basename($file).'"');

        header('Accept-Ranges: bytes');

        if ($partial) 
        {
            header('HTTP/1.1 206 Partial Content'); 
            header("Content-Range: bytes $start-$end/$filesize"); 
            
            if (!$fp = fopen($file, 'r')) 
            {
                header("HTTP/1.1 500 Internal Server Error");
                exit;
            }

            if ($start) fseek($fp,$start);

            while ($length) 
            {
                $read = ($length > 8192) ? 8192 : $length;
                $length -= $read;
                print(fread($fp,$read));
            }
            fclose($fp);
        } 
        else 
            readfile($file);

        exit;

    }

}